﻿#region Using Directives
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Text;
using System.Collections.Generic;

using Xeno_Race.Helpers;
using Xeno_Race.Graphics;
#endregion

namespace Xeno_Race.Game
{
    public class CollisionTrack
    {
        /// <summary>
        /// Scaling of the whole collision track
        /// </summary>
        public float TrackScaleFactor
        {
            get { return trackScaleFactor; }
            set { trackScaleFactor = value; }
        }
        private float trackScaleFactor = 10000.0f;

        /// <summary>
        /// Width of the track
        /// </summary>
        public float HalfTrackWidth
        {
            get { return halfTrackWidth; }
            set { halfTrackWidth = value; }
        }
        private float halfTrackWidth = 0.3f;

        /// <summary>
        /// Base points on which the collision track is built on
        /// </summary>
        private List<Vector3> basePoints;

        /// <summary>
        /// Interpolated track points as base for collision calculations
        /// </summary>
        public List<Vector3> ExtendedTrackPoints
        {
            get { return extendedTrackPoints; }
        }
        private List<Vector3> extendedTrackPoints;

        /// <summary>
        /// Starting position on track
        /// </summary>
        public Vector3 TrackStartPoint
        {
            get { return trackStartPoint; }
        }
        private Vector3 trackStartPoint;

        /// <summary>
        /// Starting direction based on starting pos
        /// </summary>
        public Vector3 TrackStartDirection
        {
            get { return trackStartDirection; }
        }
        private Vector3 trackStartDirection;

        /// <summary>
        /// Textured vertices to draw the track ingame
        /// </summary>
        private VertexPositionNormalTexture[] trackVertices, trackRightSideVertices, trackLeftSideVertices;
        /// <summary>
        /// Gets TrackVertices
        /// </summary>
        public VertexPositionNormalTexture[] TrackVertices
        {
            get { return trackVertices; }
        }
        /// <summary>
        /// Gets TrackRightSideVertices
        /// </summary>
        public VertexPositionNormalTexture[] TrackRightSideVertices
        {
            get { return trackRightSideVertices; }
        }
        /// <summary>
        /// Gets TrackLeftSideVertices
        /// </summary>
        public VertexPositionNormalTexture[] TrackLeftSideVertices
        {
            get { return trackLeftSideVertices; }
        }

        /// <summary>
        /// Default constructor, loads 8-shaped test track
        /// </summary>
        public CollisionTrack()
        {
            // Basic testshape of track positions
            // Because we always need a previous point for interpolations,
            // the interpolated track will actually start at the second base point!
            basePoints = new List<Vector3>();
                basePoints.Add(new Vector3(2, 0, 4));
                basePoints.Add(new Vector3(0, 0, 0));
                basePoints.Add(new Vector3(-2, 0, -4));
                basePoints.Add(new Vector3(2, 0, -4));
                basePoints.Add(new Vector3(0, 1, 0));
                basePoints.Add(new Vector3(-2, 0, 4));

            generateTrack();
        }

        /// <summary>
        /// Constructor for external base track point data
        /// </summary>
        public CollisionTrack(List<Vector3> loadedBasePoints, float trackScaleFactor, float halfTrackWidth)
        {
            basePoints = loadedBasePoints;
            this.trackScaleFactor = trackScaleFactor;
            this.halfTrackWidth = halfTrackWidth;

            generateTrack();
        }

        /// <summary>
        /// Generates all required Informations
        /// </summary>
        public void generateTrack()
        {
            // Scale width of track
            halfTrackWidth *= trackScaleFactor;

            // Scale base points and interpolate points inbetween
            extendedTrackPoints = GenerateTrackPoints(basePoints);

            // Set the first trackpoint as starting position
            trackStartPoint = extendedTrackPoints[0];
            trackStartDirection = extendedTrackPoints[1] - extendedTrackPoints[0];

            // Generate track floor
            trackVertices = generateTrackVertices();

            // Generate side walls of track
            trackRightSideVertices = generateTrackWallVertices(true);
            trackLeftSideVertices = generateTrackWallVertices(false);
        }

        /// <summary>
        /// Generates Track points
        /// </summary>
        private List<Vector3> GenerateTrackPoints(List<Vector3> basePoints)
        {
            basePoints.Add(basePoints[0]);
            basePoints.Add(basePoints[1]);
            basePoints.Add(basePoints[2]);

            // Scale all the basepoints
            for (int i = 0; i < basePoints.Count; i++)
            {
                basePoints[i] *= trackScaleFactor;
            }

            List<Vector3> allPoints = new List<Vector3>();

            for (int i = 1; i < basePoints.Count - 2; i++)
            {
                List<Vector3> part = InterpolateCR(basePoints[i - 1], basePoints[i], basePoints[i + 1], basePoints[i + 2]);
                allPoints.AddRange(part);
            }
            return allPoints;
        }

        /// <summary>
        /// Interpolates more points by using catmull-rom
        /// </summary>
        private List<Vector3> InterpolateCR(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
        {
            List<Vector3> list = new List<Vector3>();
            int detail = 8;
            for (int i = 0; i < detail; i++)
            {
                Vector3 newPoint = CR3D(v1, v2, v3, v4, (float)i / (float)detail);
                list.Add(newPoint);
            }
            //list.Add(v3);
            return list;
        }

        /// <summary>
        /// Interpolate method for vector3s
        /// </summary>
        private Vector3 CR3D(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, float amount)
        {
            Vector3 result = new Vector3();
            result.X = MathHelper.CatmullRom(v1.X, v2.X, v3.X, v4.X, amount);
            result.Y = MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amount);
            result.Z = MathHelper.CatmullRom(v1.Z, v2.Z, v3.Z, v4.Z, amount);

            return result;
        }

        #region Generate track vertices
        /// <summary>
        /// Get vertices with tex coords
        /// </summary>
        private VertexPositionNormalTexture[] generateTrackVertices()
        {
            float textureLength = 0.5f * trackScaleFactor;
            float distance = 0;
            List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture>();
            Vector3 currentNormal = Vector3.Up;

            for (int i = 1; i < extendedTrackPoints.Count - 1; i++)
            {
                currentNormal = Vector3.Up;
                Vector3 shipDir = extendedTrackPoints[i + 1] - extendedTrackPoints[i];
                shipDir.Normalize();

                Vector3 sideDir = Vector3.Cross(currentNormal, shipDir);
                sideDir.Normalize();
                currentNormal = Vector3.Cross(shipDir, sideDir);
                currentNormal.Normalize();

                Vector3 outerPoint = extendedTrackPoints[i] + sideDir * halfTrackWidth;
                Vector3 innerPoint = extendedTrackPoints[i] - sideDir * halfTrackWidth;

                VertexPositionNormalTexture vertex;
                vertex = new VertexPositionNormalTexture(innerPoint, currentNormal, new Vector2(0, distance / textureLength));
                verticesList.Add(vertex);
                vertex = new VertexPositionNormalTexture(outerPoint, currentNormal, new Vector2(1, distance / textureLength));
                verticesList.Add(vertex);
                distance += shipDir.Length();
            }

            // Additional vertices to close track
            VertexPositionNormalTexture extraVert = verticesList[0];
            extraVert.TextureCoordinate.Y = distance / textureLength;
            verticesList.Add(extraVert);

            extraVert = verticesList[1];
            extraVert.TextureCoordinate.Y = distance / textureLength;
            verticesList.Add(extraVert);

            return verticesList.ToArray();
        }

        /// <summary>
        /// Get wall vertices with tex coords
        /// </summary>
        private VertexPositionNormalTexture[] generateTrackWallVertices(bool right)
        {
            float textureLength = 0.5f * trackScaleFactor;
            float distance = 0;
            List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture>();
            Vector3 currentNormal = Vector3.Up;

            for (int i = 1; i < extendedTrackPoints.Count - 1; i++)
            {
                Vector3 shipDir = extendedTrackPoints[i + 1] - extendedTrackPoints[i];
                Vector3 sideDir = Vector3.Cross(currentNormal, shipDir);
                sideDir.Normalize();

                Vector3 outerPoint;
                Vector3 innerPoint;

                if (right)
                {
                    outerPoint = extendedTrackPoints[i] - sideDir * halfTrackWidth + currentNormal * trackScaleFactor;
                    innerPoint = extendedTrackPoints[i] - sideDir * halfTrackWidth;
                }
                else
                {
                    outerPoint = extendedTrackPoints[i] + sideDir * halfTrackWidth + currentNormal * trackScaleFactor;
                    innerPoint = extendedTrackPoints[i] + sideDir * halfTrackWidth;
                }

                VertexPositionNormalTexture vertex;
                vertex = new VertexPositionNormalTexture(innerPoint, currentNormal, new Vector2(0, distance / textureLength));
                verticesList.Add(vertex);
                vertex = new VertexPositionNormalTexture(outerPoint, currentNormal, new Vector2(1, distance / textureLength));
                verticesList.Add(vertex);
                distance += shipDir.Length();
            }
            
            // Additional vertices to close track
            VertexPositionNormalTexture extraVert = verticesList[0];
            extraVert.TextureCoordinate.Y = distance / textureLength;
            verticesList.Add(extraVert);

            extraVert = verticesList[1];
            extraVert.TextureCoordinate.Y = distance / textureLength;
            verticesList.Add(extraVert);

            return verticesList.ToArray();
        }
        #endregion

    }
}

